package cn.tedu.tmall.admin.mall.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Aspect // 标记当前类是一个切面类
@Component // 标记当前类是一个组件类，则Spring会对其进行管理
public class TimerAspect {

    // 连接点（JoinPoint）：程序执行过程中的某个节点，例如某个方法被调用，或抛出了异常
    // 切入点（PointCut）：匹配1个或多个连接点的表达式，具体表现为execution表达式
    // -------------------------------------------------------------
    // AOP中存在5种通知（Advice），使用不同的通知，切面方法的声明也会不同
    // -- @Around：包裹，环绕
    // -- @Before：在……之前
    // -- @After：在……之后
    // -- @AfterReturning：在返回之后
    // -- @AfterThrowing：在抛出异常之后
    // -------------------------------------------------------------
    // 各通知的执行大致如下：
    // @Around：开始
    // try {
    //     @Before
    //     连接点方法
    //     @AfterReturning
    // } catch (Throwable e) {
    //     @AfterThrowing
    // } finally {
    //     @After
    // }
    // @Around：结束
    // -------------------------------------------------------------
    // 关于切入点表达式：
    // -- 表达式的格式是：[修饰符] 返回值类型 [包名][类名]方法名(参数列表) [抛出的异常]
    // -- 可以使用 * 作为通配符，必须且只能匹配1次
    // -- 可以使用 .. 作为通配符，可以匹配n次（n >= 0），只能用于包名和参数列表
    // -------------------------------------------------------------
    //       execution表达式，用于匹配Service中的方法
    //                 ↓ 方法的返回值类型
    //                   ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 包
    //                                                    ↓ 类名
    //                                                      ↓ 方法名
    //                                                        ↓↓ 参数列表
    @Around("execution(* cn.tedu.tmall.admin.mall.service.*.*(..))")
    public Object timer(ProceedingJoinPoint pjp) throws Throwable {
        // 通过ProceedingJoinPoint获取连接点的相关信息
        Class<?> className = pjp.getTarget().getClass();
        String methodName = pjp.getSignature().getName();
        Object[] args = pjp.getArgs();
        System.out.println("类名：" + className);
        System.out.println("方法名：" + methodName);
        System.out.println("参数列表：" + Arrays.toString(args));

        // 记录执行的起始时间
        long start = System.currentTimeMillis();

        // 执行表达式匹配的方法
        // 注意-1：调用参数的proceed()方法时，必须获取返回值，并且作为当前方法的返回值
        // 注意-2：调用参数的proceed()方法时，此方法会抛出异常，必须将其抛出，不可以使用try...catch捕获并处理（除非你捕获以后马上再抛出一个异常）
        Object result = pjp.proceed();

        // 记录执行的结束时间
        long end = System.currentTimeMillis();

        // 输出
        System.out.println("执行耗时：" + (end - start));
        return result;
    }

}
